package com.demo.redis.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具类
 *
 * @author ruoyi
 **/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class PrefixRedisUtils {

    private final Long LOCKTIMEOUT = 1000 * 60 * 10L;

    @Resource(name = "redisTemplate2")
    private RedisTemplate redisTemplate;

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     * @return 缓存的对象
     */
    public <T> ValueOperations<String, T> setCacheObject(String key, T value) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        operation.set(key, value);
        return operation;
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     * @return 缓存的对象
     */
    public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        operation.set(key, value, timeout, timeUnit);
        return operation;
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public void deleteObject(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection
     */
    public void deleteObject(Collection collection) {
        redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) {
        ListOperations listOperation = redisTemplate.opsForList();
        if (null != dataList) {
            int size = dataList.size();
            for (int i = 0; i < size; i++) {
                listOperation.leftPush(key, dataList.get(i));
            }
        }
        return listOperation;
    }

    /**
     * 缓存List数据
     *
     * @param key   缓存的键值
     * @param value 待缓存到Redis中List的数据
     * @return 缓存的对象
     */
    public ListOperations setLeftCacheList(String key, Object value) {
        ListOperations listOperation = redisTemplate.opsForList();
        listOperation.leftPush(key, value);
        return listOperation;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public String getRightCacheList(String key) {
        ListOperations listOperation = redisTemplate.opsForList();
        Object o = listOperation.rightPop(key);
        if (o != null) {
            return o.toString();
        }
        return null;
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 向集合添加一个成员
     *
     * @param key     缓存键值
     * @param value 缓存的数据
     * @return 缓存数据的对象
     */
    public void sAdd(String key, Object value) {
        SetOperations setOperations = redisTemplate.opsForSet();
        setOperations.add(key, value);
    }

    /**
     * 移除集合中一个成员
     *
     * @param key     缓存键值
     * @param value 缓存的数据
     * @return
     */
    public long sRem(String key, Object value) {
        SetOperations setOperations = redisTemplate.opsForSet();
        return setOperations.remove(key, value);
    }

    /**
     * 判断 value 元素是否是集合 key 的成员
     *
     * @param key     缓存键值
     * @param value 缓存的数据
     * @return
     */
    public boolean isMember(String key, Object value) {
        SetOperations setOperations = redisTemplate.opsForSet();
        return setOperations.isMember(key, value);
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(String key) {
        BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
        Set<T> dataSet = operation.members();
        return dataSet;
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     * @return
     */
    public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        if (null != dataMap) {
            for (Map.Entry<String, T> entry : dataMap.entrySet()) {
                hashOperations.put(key, entry.getKey(), entry.getValue());
            }
        }
        return hashOperations;
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(String key) {
        Map<String, T> map = redisTemplate.opsForHash().entries(key);
        return map;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 设置递增
     *
     * @param key
     * @return
     */
    public Long incr(String key) {
        Long value = redisTemplate.opsForValue().increment(key);
        return value;
    }

    /**
     * 设置递增
     *
     * @param key
     * @return
     */
    public Long incr(String key, long timeout, TimeUnit timeUnit) {
        Long value = redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, timeout, timeUnit);
        return value;
    }

    /**
     * 设置递增
     *
     * @param key
     * @return
     */
    public Long decr(String key) {
        Long value = redisTemplate.opsForValue().decrement(key);
        return value;
    }

    /**
     * 添加一个元素, zset与set最大的区别就是每个元素都有一个score，因此有个排序的辅助功能;  zadd
     *
     * @param key
     * @param value
     * @param score
     */
    public void zadd(String key, Object value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 删除元素 zrem
     *
     * @param key
     * @param value
     */
    public void zrem(String key, Object value) {
        redisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * score的增加or减少 zincrby
     *
     * @param key
     * @param value
     * @param score
     */
    public Double zincr(String key, Object value, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    /**
     * 查询value对应的score   zscore
     *
     * @param key
     * @param value
     * @return
     */
    public Double zscore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 判断value在zset中的下标  zindex
     *
     * @param key
     * @param value
     * @return
     */
    public Long zindex(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 判断value在zset中的排名  zrank
     *
     * @param key
     * @param value
     * @return
     */
    public Long zrank(String key, Object value) {
        Long aLong = redisTemplate.opsForZSet().reverseRank(key, value);
        Long rank = aLong == null ? 0L : aLong + 1;

        return rank;
    }

    /**
     * 返回集合的长度
     *
     * @param key
     * @return
     */
    public Long zsize(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 查询集合中指定顺序的值， 0 -1 表示获取全部的集合内容  zrange
     *
     * 返回有序的集合，score小的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zrange(String key, int start, int end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 查询集合中指定顺序的值和score，0, -1 表示获取全部的集合内容
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<String>> zrangeWithScore(String key, int start, int end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 查询集合中指定顺序的值  zrevrange
     *
     * 返回有序的集合中，score大的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zrevRange(String key, int start, int end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 根据score的值，来获取满足条件的集合  zrangebyscore
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> zsortRange(String key, int min, int max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 尝试获取锁，立即返回结果
     *
     * @param key 锁key
     * @return boolean
     */
    public boolean tryLock(String key) {
        if (StringUtils.isEmpty(key)) {
            return true;
        }
        return lock(key, LOCKTIMEOUT, TimeUnit.MILLISECONDS);
    }

    /**
     * 尝试获取锁，立即返回结果, 默认上锁600s
     *
     * 其他时间的非自旋锁（尝试获取锁，立即返回结果），建议手动调用
     * lock(String key, String val, long time, TimeUnit unit)
     *
     * @param key 锁key
     * @param val 锁val
     * @return boolean
     */
    public boolean tryLock(String key, String val) {
        if (StringUtils.isEmpty(key)) {
            return true;
        }
        return lock(key, val, LOCKTIMEOUT, TimeUnit.MILLISECONDS);
    }

    /**
     * 尝试获取锁，立即返回结果，指定锁过期时间
     *
     * @param key  key
     * @param val  val
     * @param time 锁过期时间
     * @param unit 时间单位
     * @return boolean
     */
    public boolean lock(String key, String val, long time, TimeUnit unit) {

        long lockTime = unit.toMillis(time);

        final ValueOperations<String, String> ops = redisTemplate.opsForValue();

        // 尝试上锁
        if (ops.setIfAbsent(key, val, lockTime, TimeUnit.MILLISECONDS)) {
            return true;
        }

        // 上锁失败
        return false;
    }

    /**
     * 尝试获取锁，立即返回结果，指定锁过期时间
     *
     * @param key  key
     * @param time 锁过期时间
     * @param unit 时间单位
     * @return boolean
     */
    public boolean lock(String key, long time, TimeUnit unit) {
        long lockTime = unit.toMillis(time);
        final ValueOperations<String, String> ops = redisTemplate.opsForValue();
        if (ops.setIfAbsent(key, getCacheValue(lockTime), LOCKTIMEOUT, TimeUnit.MILLISECONDS)) {
            return true;
        }
        String currentValue = ops.get(key);

        // 如果currentValue为空，说明锁拥有者已经释放掉锁了，可以进行再次尝试
        if (StringUtils.isEmpty(currentValue)) {
            return ops.setIfAbsent(key, getCacheValue(lockTime));
        }

        //判断上一个锁是否到期，到期尝试获取锁
        if (System.currentTimeMillis() >= getExpireTime(currentValue)) {
            //多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，它才有权利获取锁
            String oldValue = ops.getAndSet(key, getCacheValue(lockTime));
            return currentValue.equals(oldValue);
        }
        return false;
    }


    /**
     * 尝试获取锁，如果成功，立即返回，如果一直失败，等到超时之后返回
     *
     * @param key     锁key
     * @param timeout 等待时间
     * @param unit    TimeUnit
     * @return boolean
     * @throws InterruptedException InterruptedException
     */
    public boolean tryLock(String key, long timeout, TimeUnit unit) {

        long nanosTimeout = unit.toNanos(timeout);
        long lastTime = System.nanoTime();
        do {
            if (tryLock(key)) {
                return true;
            }

            long now = System.nanoTime();
            nanosTimeout -= now - lastTime;
            lastTime = now;

            //响应中断
            if (Thread.interrupted()) {
                return false;
                //                throw new InterruptedException();
            }
        } while (nanosTimeout >= 0L);

        return false;
    }

    /**
     * 尝试获取锁，如果成功，立即返回，如果一直失败，等到超时之后返回
     *
     * @param key     锁key
     * @param val     锁Value
     * @param timeout 等待时间
     * @param releaseTime 设置锁的释放时间
     * @param unit    TimeUnit
     * @return boolean
     * @throws InterruptedException InterruptedException
     */
    public boolean tryLock(String key, String val, long timeout, long releaseTime, TimeUnit unit) {

        long nanosTimeout = unit.toNanos(timeout);
        long releaseTimeout = unit.toNanos(releaseTime);
        long lastTime = System.nanoTime();
        do {
            if (lock(key, val, releaseTime, unit)) {
                return true;
            }

            long now = System.nanoTime();
            nanosTimeout -= now - lastTime;
            lastTime = now;

            //响应中断
            if (Thread.interrupted()) {
                return false;
                //                throw new InterruptedException();
            }
        } while (nanosTimeout >= 0L);

        return false;
    }

    /**
     * 获取锁，如果成功，立即返回，如果失败，则不停的尝试，直到成功为止
     *
     * @param key 锁key
     * @throws InterruptedException InterruptedException
     */
    public void lock(String key) throws InterruptedException {

        while (true) {
            if (tryLock(key)) {
                return;
            }

            //响应中断
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
        }
    }

    /**
     * 获取锁，如果成功，立即返回，如果失败，则不停的尝试，直到成功为止
     *
     * @param key 锁key
     * @param val 锁val
     * @throws InterruptedException InterruptedException
     */
    public void lock(String key, String val) throws InterruptedException {

        while (true) {
            if (tryLock(key, val)) {
                return;
            }

            //响应中断
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
        }
    }


    /**
     * 发布消息
     *
     * @param channel 频道
     * @param message 发送的消息
     */
    public void convertAndSend(String channel, String message) {
        redisTemplate.convertAndSend(channel, message);
    }

    /**
     * 进行原子性set get操作 能set并get返回true
     * 不能则返回false
     *
     * @param key      key
     * @param value    value
     * @param timeOut  过期时间
     * @param timeUnit 时间颗粒度
     * @return
     */
    public Boolean setIfAbsent(String key, String value, Long timeOut, TimeUnit timeUnit) {
        final ValueOperations<String, String> ops = redisTemplate.opsForValue();
        return ops.setIfAbsent(key, value, timeOut, timeUnit);
    }

    /**
     * 释放锁
     *
     * @param key key
     */
    public void unlock(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 释放锁
     *
     * 判断传入的值是否符合缩放锁的条件
     *
     * @param key key
     */
    public Boolean unlock(String key, String val) {
        Object cacheObject = getCacheObject(key);

        // 获取不到值
        if (ObjectUtils.isEmpty(cacheObject)) {
            return Boolean.TRUE;
        }

        String currentVal = String.valueOf(cacheObject);
        if (currentVal.equals(val)) {
            redisTemplate.delete(key);
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 获取当期线程缓存value
     *
     * @param timeout 超时时间
     * @return 缓存值
     */
    private String getCacheValue(long timeout) {
        return Thread.currentThread().getName() + ";" + String.valueOf(System.currentTimeMillis() + timeout);
    }

    /**
     * 获取过期时间
     *
     * @param cacheValue 缓存值
     * @return 过期时间
     */
    private long getExpireTime(String cacheValue) {
        String[] values = cacheValue.split(";");
        return values.length > 1 ? Long.parseLong(values[1]) : 0L;
    }


    /**
     * 只有在字段 field 不存在时，设置哈希表字段的值。
     * @param key 类似表的概念
     * @param field 类似字段的概念
     * @return boolean 是否获取成功
     */
    public boolean hPutIfAbsent(String key, String field) {
        return hPutIfAbsent(key, field, null);
    }

    /**
     * 只有在字段 field 不存在时，设置哈希表字段的值。
     *
     * @param key 类似表的概念
     * @param field 类似字段的概念
     * @param value 是否获取成功
     * @return boolean 是否获取成功
     */
    public boolean hPutIfAbsent(String key, String field, Object value) {
        return redisTemplate.opsForHash().putIfAbsent(key, field, value);
    }
}
